package at.fhooe.mc.master.clc.server.dataaccess.dao.nativedatastore;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.EmbeddedEntity;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilter;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;
import com.google.appengine.api.datastore.Query.FilterPredicate;

import at.fhooe.mc.master.clc.server.dataaccess.ErrorUtil;
import at.fhooe.mc.master.clc.server.dataaccess.dao.ITestableDao;
import at.fhooe.mc.master.clc.server.domain.ISmallEntity;
import at.fhooe.mc.master.clc.server.domain.ITestable;
import at.fhooe.mc.master.clc.server.domain.datastorenative.AbstractNativeDatastoreEntity;
import at.fhooe.mc.master.clc.server.domain.datastorenative.BigEntityDatastoreNative;
import at.fhooe.mc.master.clc.server.domain.datastorenative.EmbeddedDatastoreNativeEntity;
import at.fhooe.mc.master.clc.server.domain.datastorenative.SmallEntityDatastoreNative;

public class TestableNativeDao implements ITestableDao
{
	
	private Entity mapTestableToEntity(ITestable testable)
	{
		Entity e = new Entity(testable.getClass().getSimpleName());
		// add properties according to the specific classes
		if (testable instanceof SmallEntityDatastoreNative)
		{
			SmallEntityDatastoreNative sedn = (SmallEntityDatastoreNative)testable;
			
			e.setProperty("stringField", sedn.getStringField());
			e.setUnindexedProperty("intField", sedn.getIntField());
			e.setUnindexedProperty("doubleField", sedn.getDoubleField());
			e.setUnindexedProperty("dateField", sedn.getDateField());
		}
		else if (testable instanceof BigEntityDatastoreNative)
		{
			BigEntityDatastoreNative bedn = (BigEntityDatastoreNative)testable;
			EmbeddedEntity embed = new EmbeddedEntity();
			embed.setProperty("stringField", bedn.getEmbeddedEntity().getStringField());
			embed.setUnindexedProperty("doubleField", bedn.getEmbeddedEntity().getDoubleField());
			
			e.setUnindexedProperty("stringList1", bedn.getStringList1());
			
			// since maps cannot be just saved store the data as sets of key and values.
			Map<String, String> bednMap = bedn.getStringMap1();
			
			e.setUnindexedProperty("stringMap1-keys", bednMap.keySet());
			e.setUnindexedProperty("stringMap1-values", bednMap.values());
			e.setProperty("embeddedEntity", embed);
		}
		else 
		{
			return null;
		}
		
		return e;
	}
	
	private ITestable mapEntityToTestable(Class<? extends ITestable> testableClass, Entity entity)
	{
		if (testableClass.isAssignableFrom(SmallEntityDatastoreNative.class))
		{
			SmallEntityDatastoreNative obj = new SmallEntityDatastoreNative(
					(String)entity.getProperty("stringField"), 
					((Long)entity.getProperty("intField")).intValue(), 
					(Double)entity.getProperty("doubleField"), 
					(Date)entity.getProperty("dateField"));
			
			obj.setKey(entity.getKey());
			
			return obj;
		}
		else if (testableClass.isAssignableFrom(BigEntityDatastoreNative.class))
		{
			EmbeddedEntity embedded = (EmbeddedEntity)entity.getProperty("embeddedEntity");
			
			EmbeddedDatastoreNativeEntity embeddedObj = new EmbeddedDatastoreNativeEntity(
					(String)embedded.getProperty("stringField"), 
					(Double)embedded.getProperty("doubleField"));
			
			Map<String, String> stringMap = new HashMap<String, String>();
			List<String> keys = (List<String>)entity.getProperty("stringMap1-keys");
			List<String> values = (List<String>)entity.getProperty("stringMap1-values");
			for (int i = 0; i < keys.size(); i++)
			{
				stringMap.put(keys.get(i), values.get(i));
			}
			
			
			BigEntityDatastoreNative obj = 
					new BigEntityDatastoreNative(
							(List<String>)entity.getProperty("stringList1"),
							stringMap, 
							embeddedObj);
			
			return obj;
		}
		
		return null;
	}
	
	private void updateId(ITestable testable, Entity entity)
	{
		if (testable instanceof AbstractNativeDatastoreEntity)
		{
			((AbstractNativeDatastoreEntity) testable).setKey(entity.getKey());
		}
	}
	
	private Key getDatastoreKeyFromTestable(ITestable test)
	{
		if (test instanceof AbstractNativeDatastoreEntity)
			return ((AbstractNativeDatastoreEntity) test).getKey();
		
		return null;
	}

	@Override
	public int save(ITestable testable)
	{
		Entity e = mapTestableToEntity(testable);
		
		if (e == null)
			return ErrorUtil.NATIVE_DATASTORE_NOT_IMPLEMENTED;
		
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		datastore.put(e);
		
		// update the id
		updateId(testable, e);
		
		return 0;
	}

	@Override
	public int save(List<ITestable> testables)
	{
		List<Entity> entities = new ArrayList<Entity>();
		
		for (ITestable test : testables)
		{
			Entity e = mapTestableToEntity(test);
			if (e == null)
				return ErrorUtil.NATIVE_DATASTORE_NOT_IMPLEMENTED;
			
			entities.add(e);
		}
		
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		datastore.put(entities);
		
		// update the ids
		for (int i = 0; i < testables.size(); i++)
		{
			updateId(testables.get(i), entities.get(i));
		}
		
		return 0;
	}

	@Override
	public List<? extends ITestable> loadAll(Class<? extends ITestable> type)
	{
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

		Query q = new Query(type.getSimpleName());
		PreparedQuery pq = datastore.prepare(q);
		
		List<ITestable> testables = new ArrayList<ITestable>();
		for (Entity entity : pq.asIterable())
		{
			ITestable testable = mapEntityToTestable(type, entity);
			if (testable != null)
				testables.add(testable);
		}
		
		return testables;
	}

	@Override
	public List<? extends ITestable> loadFilteredEquals(
			Class<? extends ITestable> type, String field1, Object filter1,
			String field2, Object filter2, String field3, Object filter3,
			String field4, Object filter4)
	{
		Filter f1 = null;
		Filter f2 = null;
		Filter f3 = null;
		Filter f4 = null;
		
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		
		// init filters, at the moment only EQUALS is possible
		if (field1 != null)
		{
			f1 = new FilterPredicate(field1, FilterOperator.EQUAL, filter1);
		}
		else if (field2 != null)
		{
			f2 = new FilterPredicate(field2, FilterOperator.EQUAL, filter2);
		}
		else if (field3 != null)
		{
			f3 = new FilterPredicate(field3, FilterOperator.EQUAL, filter3);
		}
		else if (field4 != null)
		{
			f4 = new FilterPredicate(field4, FilterOperator.EQUAL, filter4);
		}
		
		CompositeFilter cf = null;
		
		if (f1 != null && f2 != null && f3 != null && f4 != null)
		{
			cf = CompositeFilterOperator.and(f1, f2, f3, f4);
		}
		else if (f1 != null && f2 != null && f3 != null)
		{
			cf = CompositeFilterOperator.and(f1, f2, f3);
		}
		else if (f1 != null && f2 != null)
		{
			cf = CompositeFilterOperator.and(f1, f2);
		}
		
		Query q = new Query(type.getSimpleName());
		if (cf != null)
			q.setFilter(cf);
		else if (f1 != null)
			q.setFilter(f1);
		
		PreparedQuery pq = datastore.prepare(q);
		
		
		List<ITestable> testables = new ArrayList<ITestable>();
		for (Entity entity : pq.asIterable())
		{
			ITestable testable = mapEntityToTestable(type, entity);
			if (testable != null)
				testables.add(testable);
		}
		
		return testables;
	}

	@Override
	public ITestable loadSpecific(Class<? extends ITestable> type, long id)
	{
		Key key = new KeyFactory.Builder(type.getSimpleName(), id).getKey();
		ITestable testable = null;
		
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		try
		{
			Entity e =  datastore.get(key);
			
			if (e == null)
				return null;
			
			return mapEntityToTestable(type, e);
			
		} catch (EntityNotFoundException e)
		{
			e.printStackTrace();
		}
		
		return testable;
	}

	@Override
	public ITestable loadSpecific(Class<? extends ITestable> type, String id)
	{
		Key key = new KeyFactory.Builder(type.getSimpleName(), id).getKey();
		ITestable testable = null;
		
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		try
		{
			Entity e =  datastore.get(key);
			
			if (e == null)
				return null;
			
			return mapEntityToTestable(type, e);
			
		} catch (EntityNotFoundException e)
		{
			e.printStackTrace();
		}
		
		return testable;
	}

	@Override
	public int delete(ITestable testable, Class<? extends ITestable> specificClass)
	{
		Key key = getDatastoreKeyFromTestable(testable);
		
		if (key == null)
			return ErrorUtil.NATIVE_DATASTORE_NO_KEY_SET;
		
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		datastore.delete(key);
		
		return 0;
	}


	@Override
	public int deleteAll(List<ITestable> toBeDeleted)
	{
		for (ITestable testable : toBeDeleted)
			delete(testable, testable.getClass());
		
		return 0;
	}

}
